﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using FlowBasis.Security.Principal;
using FlowBasis.Data;

namespace FlowBasisSampleSite.Lib.Security
{
    public class FlowBasisSampleSitePrincipal : IPrincipalWithPermissions, IPrincipalWithDisplayNameAndId, IPrincipalWithSignOutHandler
    {
        private static readonly TimeSpan s_userDataCacheTime = TimeSpan.FromMinutes(20);
        private static readonly string s_userDataHolderCachePrefix = typeof(FlowBasisSampleSitePrincipal).FullName + "-";

        private System.Security.Principal.IIdentity identity;

        private UserDataHolder userDataHolder;
        private HashSet<string> roleSet;
        private HashSet<string> permissionSet;

        public static FlowBasisSampleSitePrincipal FromIdentity(System.Security.Principal.IIdentity identity)
        {
            return new FlowBasisSampleSitePrincipal(identity);
        }

        private FlowBasisSampleSitePrincipal(System.Security.Principal.IIdentity identity)
        {
            this.identity = identity;
        }

        public System.Security.Principal.IIdentity Identity
        {
            get { return this.identity; }
        }

        public bool IsInRole(string role)
        {
            this.LoadUserDataHolderIfNecessary();

            if (this.roleSet == null)
            {
                HashSet<string> newRoleSet = new HashSet<string>();

                var currentUserDataHolder = this.userDataHolder;
                if (currentUserDataHolder.Roles != null)
                {
                    foreach (string userRole in currentUserDataHolder.Roles)
                    {
                        newRoleSet.Add(userRole);
                    }
                }

                this.roleSet = newRoleSet;
            }

            return this.roleSet.Contains(role);
        }
      
        public bool HasPermission(string permissionName)
        {
            this.LoadUserDataHolderIfNecessary();

            if (this.permissionSet == null)
            {
                HashSet<string> newPermissionSet = new HashSet<string>();

                var currentUserDataHolder = this.userDataHolder;
                if (currentUserDataHolder.Permissions != null)
                {
                    foreach (string userPermissionName in currentUserDataHolder.Permissions)
                    {
                        newPermissionSet.Add(userPermissionName);
                    }
                }

                this.permissionSet = newPermissionSet;
            }

            return this.permissionSet.Contains(permissionName);
        }

        public string DisplayName
        {
            get
            {
                this.LoadUserDataHolderIfNecessary();
                return this.userDataHolder.DisplayName;
            }
        }

        public object UserId
        {
            get
            {
                this.LoadUserDataHolderFromDataService();
                return this.userDataHolder.UserId;
            }
        }


        public void HandleSignOut()
        {
            var httpContext = HttpContext.Current;
            if (httpContext != null)
            {
                httpContext.Cache.Remove(this.UserDataHolderCacheKey);
            }
        }


        private string UserDataHolderCacheKey
        {
            get { return s_userDataHolderCachePrefix + this.identity.Name; }
        }


        private void LoadUserDataHolderIfNecessary()
        {
            if (this.userDataHolder == null)
            {
                this.TryLoadUserDataHolderFromCache();
                if (this.userDataHolder == null)
                {
                    this.LoadUserDataHolderFromDataService();
                }
            }
        }

        private void TryLoadUserDataHolderFromCache()
        {
            var httpContext = HttpContext.Current;
            if (httpContext != null)
            {
                var loadedUserDataHolder = httpContext.Cache.Get(this.UserDataHolderCacheKey) as UserDataHolder;
                if (loadedUserDataHolder != null)
                {
                    this.userDataHolder = loadedUserDataHolder;
                }
            }
        }

        private void LoadUserDataHolderFromDataService()
        {
            IDataService dataService = FlowBasis.Services.ServiceResolver.Instance.Get<IDataService>();

            var userAccount = dataService.GetAll<FlowBasisSampleSite.Domain.Entities.UserAccount>().Single(ua => ua.UserToken == this.identity.Name);
            if (userAccount == null || userAccount.IsDeleted)
            {
                throw new Exception("Invalid user account: " + this.identity.Name);
            }

            UserDataHolder loadedUserDataHolder = new UserDataHolder();
            loadedUserDataHolder.UserId = userAccount.UserAccountId;
            loadedUserDataHolder.UserToken = userAccount.UserToken;
            loadedUserDataHolder.DisplayName = userAccount.DisplayName;

            loadedUserDataHolder.Roles = userAccount.Roles.Select(r => r.Name).ToArray();

            HashSet<string> permissions = new HashSet<string>();
            foreach (var role in userAccount.Roles)
            {
                foreach (var permission in role.Permissions)
                {
                    permissions.Add(permission.Name);
                }
            }

            loadedUserDataHolder.Permissions = permissions.ToArray();

            this.userDataHolder = loadedUserDataHolder;
            this.permissionSet = permissions;

            var httpContext = HttpContext.Current;
            if (httpContext != null)
            {
                httpContext.Cache.Add(
                    this.UserDataHolderCacheKey,
                    loadedUserDataHolder,
                    null,
                    DateTime.UtcNow.Add(s_userDataCacheTime),
                    System.Web.Caching.Cache.NoSlidingExpiration,
                    System.Web.Caching.CacheItemPriority.Normal,
                    null);
            }
        }

        [Serializable]
        public class UserDataHolder
        {
            public object UserId;
            public string UserToken;
            public string DisplayName;     
            public string[] Roles;
            public string[] Permissions;
        }
    }

}